//------------------------------------------------------------
// Microsoft?XNA Game Studio Creator's Guide  
// by Stephen Cawood and Pat McGee 
// Copyright (c) McGraw-Hill/Osborne. All rights reserved.
// http://www.mhprofessional.com/product.php?isbn=007149071X
//------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;
using System.IO;
//using QuakeMD2;

namespace MD2Importer
{
    public class Quake2Model
    {        
        public int[,,]      miPosIndex;
        public int[,,]      miFrameUVIndex;
        
        public float[, ,]   mfFramePos;
        public float[, ,]   mfNormal;
        public float[, ,]   mfUV;
                          
        public int[]        miStartFrame;
        public int[]        miEndFrame;

        public byte[]       mbyteBuffer;
        public int          miSize;
        public const int    HEADERSIZE  = 17;
        public int[]        mtMd2Header = new int[HEADERSIZE];
        
        public int          miNumFaces;
        public int          miNumFrames;
        public int          miNumVerts;
        public int          miNumUVs;
        public int          miNumAnim;

        ArrayList           mTriArray   = new ArrayList();

        tMd2TexCoord        mUV2;
        ArrayList           mListUV     = new ArrayList();
        ArrayList           mListFrames = new ArrayList();

        public              tFrame[] mtFrame;

        public enum eHeader
        {
            fileID,					            // identifies file
            version,					        // version must be 8 for md2
            texW,				                // texture pixel w
            texH,				                // texture pixel h
            frameSz,				            // bytes per frame
            numSkins,				            // total textures
            numVerts,				            // total vertices (constant for each frame)
            numTexCoords,			            // total texture coordinates
            numtri,			                    // total faces (polygons)
            numGlCommands,			            // triangle list or triangle fans
            numFrames,				            // total animation frames
            offsetSkins,				        // texture name
            offsetTexCoords,			        // texture data offset
            offsetTriangles,			        // triangle (face) data offset
            offsetFrames,			            // frame data offset
            offsetGlCommands,		            // gl commands data offset
            offsetEnd				            // The end of the file offset
        }

       public struct tFrame 
        {
	        public int          numOfVerts;	    // total verts in model
            public int          numOfFaces;		// total faces (triangles) in model
            public int          numTexVertex;	// total texture coordinates
            public float[,]     fVert;		    // frame's vertices
            public Vector3[]    v3Norm;		    // frame's normals
            public Vector2[]    v2UV;		    // texture's UV coordinates
            public tFace[]      Face;			// faces information of the frame
        };

        public struct tFace
        {
            public int[]        posIndex;		
            public int[]        uvIndex;		
        };

        // stores references to vertex and texture arrays
        public struct tMd2Face
        {
            public short[]      posIndex;
            public short[]      uvIndex;
        };

        public struct tMd2TexCoord
        {
            public short        u, v;   // texture coordinates
        };

        // vertices for frames
        public struct tMd2Triangle
        {
            public float[]              vertex;
        };

        // stores frame name and vertices 
        public struct tMd2Frame
        {
            public char[]               strName;
            public tMd2Triangle[]       face;
        };

        public struct tMD2Model
        {
            public int numFrames;		    // total frames in model
            public int numOfMaterials;		// total materials in model
            public int numOfAnimations;		// total animations in model
            public int animIndex;		    // current animation index
            public int miFrame;		        // current frame
        };

        public struct tAnimationInfo        // instance created for each animation
        {
            public String animName;         // animation name
            public int startFrame;		    // first frame in animation
            public int endFrame;		    // last frame in animation
        };

        tAnimationInfo tAnim;
        public ArrayList mAnimArray = new ArrayList();
        public Quake2Model() { }            // default constructor

        public Quake2Model(byte[] bytes)
        {
            mbyteBuffer     = bytes;
            miSize          = mbyteBuffer.Length;


            // read summary of data stored in the md2 file
            MemoryStream ms = new MemoryStream(mbyteBuffer);
            BinaryReader br = new BinaryReader(ms);
            ms.Position = 0;

            // read the faces
            for (int i = 0; i < 17; i++)
                mtMd2Header[i] = br.ReadInt32();

            br.Close();
            ms.Close();

            miNumFaces  = mtMd2Header[(int)eHeader.numtri];
            miNumFrames = mtMd2Header[(int)eHeader.numFrames];
            miNumUVs    = mtMd2Header[(int)eHeader.numTexCoords];
            readFaceIndexes();
            readUVs();
            readXYZ();
            convertDataStructures();
            storePerVertexNormal();
        }


        /// <summary>
        /// Read frame name from *.md2 and parse it to extract animation name,
        /// frame start, and frame end. 
        /// </summary>
        /// <param name="tMD2Model">Stores total frames, animation count, 
        /// current aniamtion index, current frame</param>
        private void parseAnimations(tMD2Model tModel)
        {
            String strPrevName          = "";
            tModel.numOfAnimations      = -1;

            // num of frames is same as total frames
            for (int iFrameNum = 0; iFrameNum < tModel.numFrames; iFrameNum++)
            {
                char[]      cName       = new char[16];
                tMd2Frame   tFrame      = (tMd2Frame)mListFrames[iFrameNum];
                cName                   = tFrame.strName;
                String      szAnimName    = "";

                // remove number from the name
                for (int j = 0; j < cName.Length; j++)
                {
                    // enter this next portion of the loop when  first number is found in the name
                    string strNameChar = cName[j].ToString();
                    if (strNameChar == "0" || strNameChar == "1" || strNameChar == "2" || strNameChar == "3"
                        || strNameChar == "4" || strNameChar == "5" || strNameChar == "6"
                        || strNameChar == "7" || strNameChar == "8" || strNameChar == "9")
                    {

                        // store all letters up until the first number is found
                        for (int k = 0; k < j; k++)
                            szAnimName = szAnimName + cName[k].ToString();

                        // search through remainder of string which stores numbers and extract
                        // the frame number
                        for (int k = j; k < cName.Length; k++)
                        {
                            // get current character and enter loop if number is found in name
                            string strEnd = cName[k].ToString();
                            if (strEnd == "0" || strEnd == "1" || strEnd == "2" || strEnd == "3"
                            || strEnd  == "4" || strEnd == "5" || strEnd == "6"
                            || strEnd  == "7" || strEnd == "8" || strEnd == "9")
                            {
                                // after 1st num found in name store all chars in string till 
                                // end is reached
                                int l = k + 1;
                                while (cName[l] != '\0')
                                {
                                    strEnd = strEnd + cName[l].ToString();
                                    l++;
                                }
                                // skip last two characters because name could have a
                                // number in it. ie. 'pain1' and to avoid the null byte '\0'
                                if (strEnd.Length > 2)
                                {
                                    // add numbers to the string as characters in the string
                                    for (int m = 0; m < strEnd.Length - 2; m++)
                                    {
                                        szAnimName = szAnimName + strEnd[0].ToString();
                                    }
                                    strEnd = strEnd[strEnd.Length - 2].ToString() + strEnd[strEnd.Length - 1].ToString();
                                }
                                break; // got frame number so break
                            }
                        }
                        break;         // got frame name so break
                    }
                }

                // new frame name found or finshed all frames in list
                if (szAnimName != strPrevName || iFrameNum == tModel.numFrames - 1)
                {
                    // assign end frame number after 1st frame 
                    if (strPrevName != "")
                    {
                        tAnim.endFrame = iFrameNum;
                        mAnimArray.Add(tAnim);          // add complete animation record to array list
                    }

                    // set up new animation
                    strPrevName = szAnimName;           // store current name for comparison later
                    tModel.numOfAnimations++;           // add animation count to model
                    tAnim.animName      = szAnimName;   // store current frame name
                    tAnim.startFrame    = iFrameNum;    // store starting frame number
                }
            }

            // set up three arrays to store animation frame count, start frame, and end frame
            miNumAnim                   = mAnimArray.Count;
            miStartFrame                = new int[mAnimArray.Count];
            miEndFrame                  = new int[mAnimArray.Count];

            // populate three arrays for animation frame count, start frame, and end frame
            for(int i=0; i<miNumAnim; i++)
            {
                tAnimationInfo tTempAnim= (tAnimationInfo)mAnimArray[i];
                miStartFrame[i]         =  tTempAnim.startFrame;
                miEndFrame[i]           =  tTempAnim.endFrame;
            }
        }

        private void convertDataStructures()
        {
            tMD2Model           tModel;
            tModel              = new tMD2Model();
            tModel.numFrames    = mtMd2Header[(int)eHeader.numFrames];
            parseAnimations(tModel);

            // Create a local frame to store the first frame of animation's data
            mtFrame = new tFrame[mtMd2Header[(int)eHeader.numFrames]];

            // for each frame
            for (int iFrame = 0; iFrame < tModel.numFrames; iFrame++)
            {
                // store total verts, uv's, and triangles from header info
                mtFrame[iFrame].numOfVerts      = mtMd2Header[(int)eHeader.numVerts];
                mtFrame[iFrame].numTexVertex    = mtMd2Header[(int)eHeader.numTexCoords];
                mtFrame[iFrame].numOfFaces      = mtMd2Header[(int)eHeader.numtri];
                               
                // create an array big enough to the total verts and norms each frame
                mtFrame[iFrame].fVert           = new float[mtFrame[iFrame].numOfVerts, 3];
                mtFrame[iFrame].v3Norm          = new Vector3[mtFrame[iFrame].numOfVerts];

                // get frame info from array list
                tMd2Frame tFrame = (tMd2Frame)mListFrames[iFrame];

                // assign x,y,z to frame from structure in array list
                for (int j = 0; j < mtFrame[iFrame].numOfVerts; j++)
                {
                    mtFrame[iFrame].fVert[j, 0] = tFrame.face[j].vertex[0];
                    mtFrame[iFrame].fVert[j, 1] = tFrame.face[j].vertex[1];
                    mtFrame[iFrame].fVert[j, 2] = tFrame.face[j].vertex[2];
                }

                // create UV array for current frame with enough space for all tex coords
                mtFrame[iFrame].v2UV = new Vector2[mtMd2Header[(int)eHeader.numTexCoords]];

                // md2 stores pixels for uv's. to map them onto the surface this needs
                // to be converted to a ratio that ranges between 0 and 1. To do this,
                // divide each U and V by the pixel width and height.
                for (int j = 0; j < mtMd2Header[(int)eHeader.numTexCoords]; j++)
                {
                    tMd2TexCoord px           = (tMd2TexCoord)mListUV[j];
                    mtFrame[iFrame].v2UV[j].X = px.u/(float)mtMd2Header[(int)eHeader.texW];
                    mtFrame[iFrame].v2UV[j].Y = px.v/(float)mtMd2Header[(int)eHeader.texH];
                }

                // each flat surface (or triangle) in the model is a face
                // declare an array to store all faces in the frame
                mtFrame[iFrame].Face = new tFace[mtMd2Header[(int)eHeader.numtri]];


                for (int iTri = 0; iTri < mtMd2Header[(int)eHeader.numtri]; iTri++)
                {
                    // store xyz indices for each of the three triangle coordinates
                    mtFrame[iFrame].Face[iTri].posIndex = new int[3];

                    tMd2Face c = (tMd2Face)mTriArray[iTri];

                    mtFrame[iFrame].Face[iTri].posIndex[0] = c.posIndex[0];
                    mtFrame[iFrame].Face[iTri].posIndex[1] = c.posIndex[1];
                    mtFrame[iFrame].Face[iTri].posIndex[2] = c.posIndex[2];

                    // store all uv indices in a triangle
                    mtFrame[iFrame].Face[iTri].uvIndex    = new int[3];
                    mtFrame[iFrame].Face[iTri].uvIndex[0] = c.uvIndex[0];
                    mtFrame[iFrame].Face[iTri].uvIndex[1] = c.uvIndex[1];
                    mtFrame[iFrame].Face[iTri].uvIndex[2] = c.uvIndex[2];
                }
            }
            mListFrames.Clear();
            mTriArray.Clear();
            mListUV.Clear();
        }

        /// <summary>
        /// Computes and stores normals for each vertex each frame.
        /// </summary>
        private void storePerVertexNormal()
        {
            for (int iFrameNum = 0; iFrameNum < mtMd2Header[(int)eHeader.numFrames]; iFrameNum++)
            {
                // calculate and store normals for each vertex
                Vector3[] v3TempNormal = new Vector3[mtMd2Header[(int)eHeader.numtri]];
                for (int iTri = 0; iTri < mtMd2Header[(int)eHeader.numtri]; iTri++)
                {
                    // get the normal for the immediate face
                    Vector3 v3a = new Vector3(mtFrame[iFrameNum].fVert[mtFrame[iFrameNum].Face[iTri].posIndex[0],0],
                                  mtFrame[iFrameNum].fVert[mtFrame[iFrameNum].Face[iTri].posIndex[0],1],
                                  mtFrame[iFrameNum].fVert[mtFrame[iFrameNum].Face[iTri].posIndex[0],2]) ;

                    Vector3 v3b = new Vector3(mtFrame[iFrameNum].fVert[mtFrame[iFrameNum].Face[iTri].posIndex[1],0],
                                  mtFrame[iFrameNum].fVert[mtFrame[iFrameNum].Face[iTri].posIndex[1],1],
                                  mtFrame[iFrameNum].fVert[mtFrame[iFrameNum].Face[iTri].posIndex[1],2]);

                    Vector3 v3c = new Vector3(mtFrame[iFrameNum].fVert[mtFrame[iFrameNum].Face[iTri].posIndex[2], 0],
                                  mtFrame[iFrameNum].fVert[mtFrame[iFrameNum].Face[iTri].posIndex[2], 1],
                                  mtFrame[iFrameNum].fVert[mtFrame[iFrameNum].Face[iTri].posIndex[2], 2]);
                    
                    Vector3 v1         = v3a - v3c;
                    Vector3 v2         = v3c - v3b;
                    Vector3 v3Cross    = Vector3.Cross(v1, v2);
                    v3TempNormal[iTri] = v3Cross;
                }

                // go through all vertices
                for (int iVert = 0; iVert < mtMd2Header[(int)eHeader.numVerts]; iVert++)
                {
                    Vector3 v3Sum   = Vector3.Zero;
                    float fCounter  = 0.0f;

                    // find surrounding faces for each vertex and calculate the 
                    // average normal
                    for (int iTri = 0; iTri < mtMd2Header[(int)eHeader.numtri]; iTri++)
                    {
                        // check if any face coordinates uses the current vertex
                        if (
                            mtFrame[iFrameNum].Face[iTri].posIndex[0] == iVert ||
                            mtFrame[iFrameNum].Face[iTri].posIndex[1] == iVert ||
                            mtFrame[iFrameNum].Face[iTri].posIndex[2] == iVert){
                                // sum normals for all surrounding faces
                                v3Sum    += v3TempNormal[iTri];
                                fCounter += 1.0f;
                            }
                    }
                    
                    // get average of normals for all surrounding faces
                    Vector3 v3Avg = v3Sum/fCounter; 
                    v3Avg.Normalize();
                    mtFrame[iFrameNum].v3Norm[iVert] = v3Avg;
                }
            }
        }

        /// <summary>
        /// Reads binary XYZ from media file.  Scales and translates each frame 
        /// according to transformations stored with each frame.
        /// </summary>
        private void readXYZ()
        {
            MemoryStream ms = new MemoryStream(mbyteBuffer);
            BinaryReader br = new BinaryReader(ms);

            // read all verts for all frames
            for (int i = 0; i < mtMd2Header[(int)eHeader.numFrames]; i++) 
            {
                ms.Position                 = mtMd2Header[(int)eHeader.offsetFrames] 
                                            + i*mtMd2Header[(int)eHeader.frameSz];
                
                // each frame is scaled and translated on XYZ
                float[]         fFrameScale = new float[3];
                float[]        fFrameTransl = new float[3];
                char[]           fFrameName = new char[16];

                // read scale and tranlation information from binary file
                for (int j = 0; j < 3; j++)
                    fFrameScale[j]          = br.ReadSingle();
                for (int j = 0; j < 3; j++)
                    fFrameTransl[j]         = br.ReadSingle();

                // read frame name from media
                fFrameName                  = br.ReadChars(16);

                tMd2Frame                   tFrame;
                tFrame.face                 = new tMd2Triangle[mtMd2Header[(int)eHeader.numVerts]];
                ms.Position                 = mtMd2Header[(int)eHeader.offsetFrames]
                                            + i * mtMd2Header[(int)eHeader.frameSz] + 40;
                tFrame.strName              = fFrameName;

                // read xyz, scale and tranlate them, swap Y with Z and negate Z (by design)
                for (int j = 0; j < mtMd2Header[(int)eHeader.numVerts]; j++)
                {
                    tFrame.face[j].vertex    = new float[3];
                    tFrame.face[j].vertex[0] =   (float)br.ReadByte()*fFrameScale[0] + fFrameTransl[0];
                    tFrame.face[j].vertex[2] = -((float)br.ReadByte()*fFrameScale[1] + fFrameTransl[1]);
                    tFrame.face[j].vertex[1] =   (float)br.ReadByte()*fFrameScale[2] + fFrameTransl[2];

                    float normal             = (float)br.ReadByte();
                }
                mListFrames.Add(tFrame);
            }
            br.Close();
            ms.Close();
        }

        /// <summary>
        /// Read UV data from media file.
        /// </summary>
        private void readUVs()
        {
            MemoryStream ms = new MemoryStream(mbyteBuffer);
            BinaryReader br = new BinaryReader(ms);

            // read the texture coorindates
            ms.Position = mtMd2Header[(int)eHeader.offsetTexCoords];

            for (int i = 0; i < mtMd2Header[(int)eHeader.numTexCoords]; i++)
            {
                mUV2.u = br.ReadInt16();
                mUV2.v = br.ReadInt16();
                mListUV.Add(mUV2);
            }

            br.Close();
            ms.Close();
        }

        /// <summary>
        /// Reads indexes for UV and XYZ from media file.
        /// </summary>
        private void readFaceIndexes()
        {
            MemoryStream ms = new MemoryStream(mbyteBuffer);
            BinaryReader br = new BinaryReader(ms);

            ms.Position = mtMd2Header[(int)eHeader.offsetTriangles];

            tMd2Face mTri2;
            for (int i = 0; i < mtMd2Header[(int)eHeader.numtri]; i++)
            {
                mTri2.posIndex      = new short[3];
                mTri2.uvIndex       = new short[3];
                mTri2.posIndex[0]   = br.ReadInt16();
                mTri2.posIndex[1]   = br.ReadInt16();
                mTri2.posIndex[2]   = br.ReadInt16();
                mTri2.uvIndex[0]    = br.ReadInt16();
                mTri2.uvIndex[1]    = br.ReadInt16();
                mTri2.uvIndex[2]    = br.ReadInt16();
                mTriArray.Add(mTri2);
            }

            br.Close();
            ms.Close();
        }

        public int getNumTriangles()
        {
            return miNumFaces;
        }
    }
        
    // Loads Quake data from media file.  Reads all bytes and passes them
    // to the Quake2Model class to read the header and then load the media.
    [ContentImporter(".md2", DefaultProcessor = "md2Processor")]
    public class MD2Importer : ContentImporter<Quake2Model>
    {
        public override Quake2Model Import(String filename, ContentImporterContext context)
        {
            byte[] bytes    = File.ReadAllBytes(filename);
           Quake2Model md2  = new Quake2Model(bytes);
            return md2;
        }
    }

    // Returns media data in intermediate form
    [ContentProcessor] // <tinput, toutput>
    // ContentProcessor provides a base for developing custom processor components
    // all processor's must derive from this class
    public class md2Processor : ContentProcessor<Quake2Model, Quake2Model>
    {
        // override the Process method with our own output datatype
        public override Quake2Model Process(Quake2Model input,
                        ContentProcessorContext context)
        {
            return new Quake2Model(input.mbyteBuffer);
        }
    }

    // identify the components of a type writer
    [ContentTypeWriter]
    // ContentTypeWriter provides methods for converting to binary format for *.xnb file
    public class MD2Writer : ContentTypeWriter<Quake2Model>
    {
        public struct tAnimationInfo
        {
            public String   animName;
            public int      startFrame;		
            public int      endFrame;		
        };

        // outputs to *.xnb file
        protected override void Write(ContentWriter output, Quake2Model md2)
        {
            output.Write(md2.miSize);
            output.Write(md2.miNumFrames);
            output.Write(md2.miNumFaces);

            for (int iFrame = 0; iFrame < md2.miNumFrames; iFrame++)
            {
                for (int iTri = 0; iTri < md2.miNumFaces; iTri++)
                {
                    // store the position indices
                    output.Write(md2.mtFrame[iFrame].Face[iTri].posIndex[0]);
                    output.Write(md2.mtFrame[iFrame].Face[iTri].posIndex[1]);
                    output.Write(md2.mtFrame[iFrame].Face[iTri].posIndex[2]);

                    // store the uv indices
                    output.Write(md2.mtFrame[iFrame].Face[iTri].uvIndex[0]);
                    output.Write(md2.mtFrame[iFrame].Face[iTri].uvIndex[1]);
                    output.Write(md2.mtFrame[iFrame].Face[iTri].uvIndex[2]);
                }
            }

            // store vertex position data for all frames
            output.Write(md2.mtFrame[0].numOfVerts);
            for (int iFrame = 0; iFrame < md2.miNumFrames; iFrame++)
            {
                // store the position vertices
                for (int j = 0; j < md2.mtFrame[iFrame].numOfVerts; j++)
                {
                    output.Write(md2.mtFrame[iFrame].fVert[j, 0]);
                    output.Write(md2.mtFrame[iFrame].fVert[j, 1]);
                    output.Write(md2.mtFrame[iFrame].fVert[j, 2]);
                }
            }

            // store UV data for all frames
            output.Write(md2.miNumUVs);
            for (int iFrame = 0; iFrame < md2.miNumFrames; iFrame++)
            {
                for (int j=0; j < md2.miNumUVs; j++)
                {
                    output.Write(md2.mtFrame[iFrame].v2UV[j].X);
                    output.Write(md2.mtFrame[iFrame].v2UV[j].Y);
                }
            }

            // store normal data for all frames
            for(int iFrame = 0; iFrame < md2.miNumFrames; iFrame++)
            {
                for (int j = 0; j < md2.mtFrame[iFrame].numOfVerts; j++)
                {
                    output.Write(md2.mtFrame[iFrame].v3Norm[j].X);
                    output.Write(md2.mtFrame[iFrame].v3Norm[j].Y);
                    output.Write(md2.mtFrame[iFrame].v3Norm[j].Z);
                }
            }

            // store animation data
            output.Write(md2.miNumAnim); 
            for (int i = 0; i < md2.miNumAnim; i++)
            {
               output.Write(md2.miStartFrame[i]);
               output.Write(md2.miEndFrame[i]);
            }
          }

        public override string GetRuntimeType(TargetPlatform targetPlatform)
        {
            return typeof(Quake2Model).AssemblyQualifiedName;
        }

        public override string GetRuntimeReader(TargetPlatform targetPlatform)
        {
            return "MD2Importer.MD2Reader, MD2Importer, Version=1.0.0.0, Culture=neutral";
        }
    }

    // reads managed binary format
    public class MD2Reader : ContentTypeReader<Quake2Model>
    {
        protected override Quake2Model Read(ContentReader input, Quake2Model existingInstance)
        {
            int size            = input.ReadInt32();
            Quake2Model md2     = new Quake2Model();

            md2.miSize          = size;
            md2.miNumFrames     = input.ReadInt32();
            md2.miNumFaces      = input.ReadInt32();

            md2.miPosIndex      = new int[md2.miNumFrames, md2.miNumFaces, 3];
            md2.miFrameUVIndex  = new int[md2.miNumFrames, md2.miNumFaces, 3]; 

            for (int iFrame = 0; iFrame < md2.miNumFrames; iFrame++)
            {
                for (int iTri = 0; iTri < md2.miNumFaces; iTri++)
                {
                    md2.miPosIndex[iFrame, iTri, 0]     = input.ReadInt32();
                    md2.miPosIndex[iFrame, iTri, 1]     = input.ReadInt32();
                    md2.miPosIndex[iFrame, iTri, 2]     = input.ReadInt32();
                    md2.miFrameUVIndex[iFrame, iTri, 0] = input.ReadInt32();
                    md2.miFrameUVIndex[iFrame, iTri, 1] = input.ReadInt32();
                    md2.miFrameUVIndex[iFrame, iTri, 2] = input.ReadInt32();
                }
            }

            // read in the position coordinates
            md2.miNumVerts      = input.ReadInt32();
            md2.mfFramePos      = new float[md2.miNumFrames, md2.miNumVerts, 3];
            
            for (int iFrames = 0; iFrames < md2.miNumFrames; iFrames++)
            {
                for (int iVerts = 0; iVerts < md2.miNumVerts; iVerts++)
                {
                    md2.mfFramePos[iFrames, iVerts, 0] = input.ReadSingle();
                    md2.mfFramePos[iFrames, iVerts, 1] = input.ReadSingle();
                    md2.mfFramePos[iFrames, iVerts, 2] = input.ReadSingle();
                }
            }

            // read texture coordinates
            md2.miNumUVs = input.ReadInt32();
            md2.mfUV     = new float[md2.miNumFrames, md2.miNumUVs, 2];

            for (int iFrames = 0; iFrames < md2.miNumFrames; iFrames++)
            {
                for (int iUV = 0; iUV < md2.miNumUVs; iUV++)
                {
                    md2.mfUV[iFrames, iUV, 0] = input.ReadSingle();
                    md2.mfUV[iFrames, iUV, 1] = input.ReadSingle();
                }
            }

            md2.mfNormal = new float[md2.miNumFrames, md2.miNumVerts, 3];
            for (int iFrames = 0; iFrames < md2.miNumFrames; iFrames++)
            {
                for (int iVerts = 0; iVerts < md2.miNumVerts; iVerts++)
                {
                    md2.mfNormal[iFrames, iVerts, 0] = input.ReadSingle();
                    md2.mfNormal[iFrames, iVerts, 1] = input.ReadSingle();
                    md2.mfNormal[iFrames, iVerts, 2] = input.ReadSingle();
                }
            }

            // read animation info
            md2.miNumAnim       = input.ReadInt32();
            md2.miStartFrame    = new int [md2.miNumAnim ];
            md2.miEndFrame      = new int[md2.miNumAnim];

            // read start and end frames
            for (int i = 0; i < md2.miNumAnim; i++)
            {
                md2.miStartFrame[i] = input.ReadInt32();
                md2.miEndFrame[i]   = input.ReadInt32();
            }
            return md2;
        }
    }

}
